module OrigenTesters
  module IGXLBasedTester
    class << self
      attr_reader :pat_extension
      attr_reader :comment_char
    end
    @pat_extension = 'atp'
    @comment_char = '//'

    # This is the base class of all IGXL-based testers
    class Base
      include VectorBasedTester

      attr_accessor :software_version
      attr_accessor :pattern_compiler_pinmap
      attr_accessor :memory_test_en
      attr_accessor :testerconfig
      attr_accessor :channelmap
      attr_accessor :default_channelmap
      attr_accessor :default_testerconfig
      attr_accessor :max_site
      # permit modification of minimum repeat count
      attr_accessor :min_repeat_loop
      alias_method :min_repeat_count, :min_repeat_loop
      alias_method :min_repeat_count=, :min_repeat_loop=
      # NOTE: DO NOT USE THIS CLASS DIRECTLY ONLY USED AS PARENT FOR
      # DESIRED TESTER CLASS

      # Returns a new IGXLBasedTester instance, normally there would only ever be one of these
      # assigned to the global variable such as $tester by your target.
      def initialize
        @unique_counter = 0
        @counter_lsb_bits = 0
        @counter_msb_bits = 0
        @max_repeat_loop = 65_535 # 16 bits
        @min_repeat_loop = 2
        @pat_extension = OrigenTesters::IGXLBasedTester.pat_extension
        @active_loads = true
        @pipeline_depth = 34
        @software_version = ''
        @compress = true
        @support_repeat_previous = true
        @match_entries = 10
        @name = ''
        @program_comment_char = ['logprint', "'"]
        @opcode_mode = :extended
        @flags = %w(cpuA cpuB cpuC cpuD)
        @microcode = {}
        @microcode[:enable] = 'enable'
        @microcode[:set_flag] = 'set_cpu'
        @microcode[:mask_vector] = 'ign ifc icc'

        @mask_vector = false   # sticky option to mask all subsequent vectors

        @min_pattern_vectors = 0  # no minimum

        @memory_test_en = false  # memory test enabled (for all patterns?)

        @testerconfig ||= {}
        @channelmap ||= {}
        @pushed_instrument_configs = {}
        @overlay_style = :subroutine		# default to use subroutine for overlay
        @capture_style = :hram			# default to use hram for capture
        @overlay_history = {}			# used to track labels, subroutines, digsrc pins used etc
        @overlay_subr = nil
        @capture_history = {}
      end

      def igxl_based?
        true
      end

      def import_tester_config(testconfigname, fullconfigpath)
        # This function reads in CurrentConfig.txt file generated by IG-XL.
        # testconfigname example ==> "FT", "WT", "Production"
        # fullconfigpath example ==> "/product_folder/CurrentConfig.txt"

        puts "importing Testerconfig #{testconfigname}..."
        slotnum = Struct.new(:slot, :instrument, :idprom)
        @testerconfig[testconfigname] ||= {}
        current_config_file = Pathname.new(fullconfigpath)
        File.open(current_config_file, 'r').each_line do |line|
          if line =~ /^\d*.0/
            (slot, blank1, instrument, blank2, idprom) = line.split(/\t/)
            if (!slot.nil?) && (!instrument.nil?) && (!idprom.nil?)
              @testerconfig[testconfigname][slot.split('.').first.to_i] = slotnum.new(slot.split('.').first.to_i, instrument, idprom.chomp)
            end
          end
        end

        @default_testerconfig ||= testconfigname  # Default TesterConfig gets set if it's not nil
      end

      def get_tester_instrument(testconfigname, slot)
        @testerconfig[testconfigname].each_with_index do |element, index|
          if slot.to_s == element[0].to_s
            return element[1][:instrument]
          end
        end
        nil  # if no corresponding slot
      end

      def get_instrument_slots(testconfigname, instrument)  # testconfigname example "WT", "FT", "Production"
        @slots = []
        @testerconfig[testconfigname].each_with_index do |element, index|
          if instrument.to_s == element[1][:instrument].to_s
            @slots << element[0].to_i
          end
        end
        @slots  # if no corresponding slot
      end

      def import_chanmap(chanmapname, fullchanmappath)
        # This function reads IG-XL ChannelMap file
        # chanmapname example ==> "FT", "WT", "FTX2"
        # fullchanmappath example ==> "/product_folder/Chans_FT.txt"

        puts "importing ChannelMap #{fullchanmappath}..."

        chanassignment = Struct.new(:pinname, :site, :channel, :type, :packagepin)
        chanmap_file = Pathname.new(fullchanmappath)
        @channelmap[chanmapname] ||= {}
        File.open(chanmap_file, 'r').each_line.with_index do |line, index|
          if index == 0
            unless line =~ /DTChanMap/
              puts "#{fullchanmappath} is not a valid IG-XL ChannelMap!"
              break
            end
          end
          if index == 5
            siteloc = line.strip.split(/\t/).size - 1 # strip all white spaces and grab second to last
            @max_site_s = line.split(/\t/)[siteloc].strip.split(/\s/)[1]
            @max_site = @max_site_s.to_i
            (0..@max_site).each do |sitenum|
              @channelmap[chanmapname][sitenum] ||= {}
            end
          end
          if index > 5
            (blank1, pinname, packagepin, type) = line.split(/\t/)
            (0..@max_site).each do |sitenum|
              channel = line.split(/\t/)[4 + sitenum].to_s
              @channelmap[chanmapname][sitenum][pinname.downcase.intern] = chanassignment.new(pinname.downcase.intern, sitenum, channel.chomp, type.chomp, packagepin)
            end
          end
        end
        @default_channelmap ||= chanmapname # Default Channelmap gets set if it's not nil
      end

      def get_tester_channel(chanmapname, pinname, sitenum)
        if sitenum <= @max_site
          @testerchannel = @channelmap[chanmapname][sitenum][pinname].channel
          return @testerchannel
        else
          return nil
        end
      end

      # Check if a specific pin for a given channelmap for a given site number is using merged channel.
      # If yes, return x2 for Merged2, x4 for Merged4, etc.  If no, return nil.
      def merged_channels(chanmapname, pinname, sitenum)
        if sitenum <= @max_site
          if @channelmap[chanmapname][sitenum][pinname].type.include?('Merged')
            @merged_channels = @channelmap[chanmapname][sitenum][pinname].type.split('Merged')[1]
            return 'x' + @merged_channels
          end
        else
          return nil
        end
      end

      # Check if a specific HexVS supply is HexVS+ variety, which has +/-2mV accuracy as opposed to
      # +/-7mV accuracy.
      # If the specific HexVS is HexVS+ variety, returns a "+" string, otherwise nil.
      def is_hexvs_plus(testconfigname, slot)
        if @testerconfig[testconfigname][slot][:instrument].to_s == 'HexVS'
          @productnum = @testerconfig[testconfigname][slot][:idprom].split(' ')[0]
          if (@productnum.include?('974-294-')) && (@productnum.split('-')[2].to_i >= 20)
            return '+'
          end
        end
        nil  # if nothing matched
      end

      # Check if a specific VHDVS (UVS256) supply is High-Accuracy (HA) variety, which has +/-5mV+0.1%*SUPPLY accuracy as opposed to
      # +/-10mV+0.1%*SUPPLY accuracy.
      # If the specific VHDVS is of High-Accuracy variety, returns a "+" string, otherwise nil.
      def is_vhdvs_plus(testconfigname, slot)
        if @testerconfig[testconfigname][slot][:instrument].to_s == 'VHDVS'
          @productnum = @testerconfig[testconfigname][slot][:idprom].split(' ')[0]
          # binding.pry
          if (@productnum.include?('805-052-')) && (@productnum.split('-')[2].to_i >= 05)
            return '+'
          end
        end
        nil  # if nothing matched
      end
      # Check if a specific VHDVS (UVS256) channel assignment is _HC variety (high-current)
      # If the specific VHDVS channel is _HC variety, returns a "_HC" string, otherwise nil.
      def is_vhdvs_hc(chanmapname, pinname, sitenum)
        if sitenum <= @max_site
          if @channelmap[chanmapname][sitenum][pinname].channel.downcase.include?('hc')
            return '_HC'
          end
        end
        nil  # if nothing matched
      end

      def assign_dc_instr_pins(dc_pins)
        if !dc_pins.is_a?(Array)
          @dc_pins = [] << dc_pins
        else
          @dc_pins = dc_pins
        end
      end

      def assign_digsrc_pins(digsrc_pins)
        if !digsrc_pins.is_a?(Array)
          @digsrc_pins = [] << digsrc_pins
        else
          @digsrc_pins = digsrc_pins
        end
      end

      def assign_digcap_pins(digcap_pins)
        if !digcap_pins.is_a?(Array)
          @digcap_pins = [] << digcap_pins
        else
          @digcap_pins = digcap_pins
        end
      end

      def get_dc_instr_pins
        @dc_pins
      end

      def get_digsrc_pins
        @digsrc_pins
      end

      def get_digcap_pins
        @digcap_pins
      end

      def flows
        parser.flows
      end

      # Main accessor to all content parsed from existing test program sheets found in the
      # supplied directory or in Origen.config.test_program_output_directory
      def parser(prog_dir = Origen.config.test_program_output_directory)
        unless prog_dir
          fail 'You must supply the directory containing the test program sheets, or define it via Origen.config.test_program_output_directory'
        end
        @parser ||= IGXLBasedTester::Parser.new
        @parsed_dir ||= false
        if @parsed_dir != prog_dir
          @parser.parse(prog_dir)
          @parsed_dir = prog_dir
        end
        @parser
      end

      # Capture a vector to the tester HRAM.
      #
      # This method applies a store vector (stv) opcode to the previous vector, note that is does
      # not actually generate a new vector.
      #
      # Sometimes when generating vectors within a loop you may want to apply a stv opcode
      # retrospectively to a previous vector, passing in an offset option will allow you
      # to do this.
      #
      # On J750 the pins argument is ignored since the tester only supports whole vector capture.
      #
      # @example
      #   $tester.cycle                # This is the vector you want to capture
      #   $tester.store                # This applies the STV opcode
      #
      #   $tester.cycle                # This one gets stored
      #   $tester.cycle
      #   $tester.cycle
      #   $tester.store(:offset => -2) # Just realized I need to capture that earlier vector
      def store(*pins)
        options = pins.last.is_a?(Hash) ? pins.pop : {}
        options = { offset: 0
                  }.merge(options)
        update_vector microcode: 'stv', offset: options[:offset]
        last_vector(options[:offset]).contains_capture = true unless @inhibit_vectors
      end
      alias_method :to_hram, :store
      alias_method :capture, :store

      # Capture the next vector generated to HRAM
      #
      # This method applies a store vector (stv) opcode to the next vector to be generated,
      # note that is does not actually generate a new vector.
      #
      # On J750 the pins argument is ignored since the tester only supports whole vector capture.
      #
      # @example
      #   $tester.store_next_cycle
      #   $tester.cycle                # This is the vector that will be captured
      def store_next_cycle(*pins)
        options = pins.last.is_a?(Hash) ? pins.pop : {}
        options = {
        }.merge(options)

        unless @inhibit_vectors
          preset_next_vector microcode: 'stv' do |vector|
            vector.contains_capture = true
          end
        end
      end
      alias_method :store!, :store_next_cycle

      # @api private
      def remove_store_from_vector(vector)
        super
        vector.microcode = nil
      end

      # Call a subroutine.
      #
      # This method applies a call subroutine opcode to the previous vector, it does not
      # generate a new vector.
      #
      # Subroutines should always be called through this method as it ensures a running
      # log of called subroutines is maintained and which then gets output in the pattern
      # header to import the right dependencies.
      #
      # An offset option is available to make the call on earlier vectors.
      #
      # ==== Examples
      #   $tester.call_subroutine("mysub")
      #   $tester.call_subroutine("my_other_sub", :offset => -1)
      def call_subroutine(name, options = {})
        options = {
          offset: 0
        }.merge(options)
        unless @inhibit_vectors
          called_subroutines << name.to_s.chomp unless called_subroutines.include?(name.to_s.chomp) || @inhibit_vectors
          update_vector microcode: "call #{name}", offset: options[:offset]
        end
      end

      # Start a subroutine.
      #
      # Generates a global subroutine label. Global is used to adhere to the best practice of
      # containing all subroutines in dedicated patterns, e.g. global_subs.atp
      #
      # ==== Examples
      #     $tester.start_subroutine("wait_for_done")
      #     < generate your subroutine vectors here >
      #     $tester.end_subroutine
      def start_subroutine(name, options = {})
        unless @inhibit_vectors
          local_subroutines << name.to_s.chomp unless local_subroutines.include?(name.to_s.chomp) || @inhibit_vectors
          if $tester.ultraflex? && (name =~ /keep_?alive/ || options[:keep_alive])
            microcode "keepalive subr #{name}:"
          else
            microcode "global subr #{name}:"
          end
        end
      end

      # End a subroutine.
      #
      # Generates a return opcode on the last vector.
      #
      # ==== Examples
      #     $tester.start_subroutine("wait_for_done")
      #     < generate your subroutine vectors here >
      #     $tester.end_subroutine
      # cond: whether return is conditional on a flag (to permit to mix subrs together)
      def end_subroutine(cond = false, options = {})
        (cond, options) = false, cond if cond.is_a?(Hash)
        if cond
          update_vector microcode: 'if (flag) return'
        else
          update_vector microcode: 'return'
        end
      end

      # Do a frequency measure.
      #
      # Write the necessary micro code to do a frequency measure on the given pin,
      # optionally supply a read code to pass information to the tester.
      #
      # ==== Examples
      #   $tester.freq_count($top.pin(:d_out))                 # Freq measure on pin "d_out"
      #   $tester.freq_count($top.pin(:d_out):readcode => 10)
      def freq_count(pin, options = {})
        options = { readcode: false
                  }.merge(options)

        set_code(options[:readcode]) if options[:readcode]
        cycle(microcode: "#{@microcode[:set_flag]} (cpuA)")
        cycle(microcode: "#{@microcode[:set_flag]} (cpuA)")
        cycle(microcode: "#{@microcode[:set_flag]} (cpuB)")
        cycle(microcode: "#{@microcode[:set_flag]} (cpuC)")
        cycle(microcode: 'freq_loop_1:')
        cycle(microcode: 'if (cpuA) jump freq_loop_1')
        pin.drive_lo
        delay(2000)
        pin.dont_care
        cycle(microcode: "freq_loop_2: #{@microcode[:enable]} (#{@flags[1]})")
        cycle(microcode: 'if (flag) jump freq_loop_2')
        cycle(microcode: "#{@microcode[:enable]} (#{@flags[2]})")
        cycle(microcode: 'if (flag) jump freq_loop_1')
      end

      # * J750 Specific *
      #
      # Generates a single MTO opcode line for J750
      #
      # Codes implemented: xa load_preset, xa inc, ya load_preset, ya inc, stv_m0, stv_m1, stv_c<br>
      def memory_test(options = {})
        options = {
          gen_vector:          true,                 # Default generate vector not just MTO opcode
          init_counter_x:      false,            # initialize counter X
          inc_counter_x:       false,             # increment counter X
          init_counter_y:      false,            # initialize counter X
          inc_counter_y:       false,             # increment counter X
          capture_vector:      false,            # capture vector to memory using all mem types
          capture_vector_mem0: false,       # capture vector to memory type 0, here for J750 will be stv_m0
          capture_vector_mem1: false,       # capture vector to memory type 1, here for J750 will be stv_m1
          capture_vector_mem2: false,       # capture vector to memory type 2, here for J750 will be stv_c
          pin:                 false,                       # pin on which to drive or expect data, pass pin object here!
          pin_data:            false,                  # pin data (:none, :drive, :expect)
        }.merge(options)

        mto_opcode = ''

        if options[:init_counter_x]
          mto_opcode += ' xa load_preset'
        end
        if options[:inc_counter_x]
          mto_opcode += ' xa inc'
        end
        if options[:init_counter_y]
          mto_opcode += ' ya load_preset'
        end
        if options[:inc_counter_y]
          mto_opcode += ' ya inc'
        end
        if options[:capture_vector]
          mto_opcode += ' stv_m0 stv_m1 stv_c'
        end
        if options[:capture_vector_mem0]
          mto_opcode += ' stv_m0'
        end
        if options[:capture_vector_mem1]
          mto_opcode += ' stv_m1'
        end
        if options[:capture_vector_mem2]
          mto_opcode += ' stv_c'
        end

        unless mto_opcode.eql?('')
          mto_opcode = '(mto:' + mto_opcode + ')'
        end

        if options[:gen_vector]
          if options[:pin]
            case options[:pin_data]
              when :drive
                # store current pin state
                cur_pin_state = options[:pin].state.to_sym
                options[:pin].drive_mem
              when :expect
                # store current pin state
                cur_pin_state = options[:pin].state.to_sym
                options[:pin].expect_mem
            end
          end
          cycle(microcode: "#{mto_opcode}")
          if options[:pin]
            # restore previous pin state
            case options[:pin_data]
              when :drive
                options[:pin].state = cur_pin_state
              when :expect
                options[:pin].state = cur_pin_state
            end
          end
        else
          microcode "#{mto_opcode}"
        end
      end

      # Generates a match loop on up to two pins.
      #
      # This method is not really intended to be called directly, rather you should call
      # via Tester#wait e.g. $tester.wait(:match => true).
      #
      # The timeout should be provided in cycles, however when called via the wait method the
      # time-based helpers (time_in_us, etc) will be converted to cycles for you.
      # The following options are available to tailor the match loop behavior, defaults in
      # parenthesis:
      # * :pin - The pin object to match on (*required*)
      # * :state - The pin state to match on, :low or :high (*required*)
      # * :pin2 (nil) - Optionally supply a second pin to match on
      # * :state2 (nil) - State for the second pin (required if :pin2 is supplied)
      # * :check_for_fails (false) - Flushes the pipeline and handshakes with the tester (passing readcode 100) prior to the match (to allow binout of fails encountered before the match)
      # * :force_fail_on_timeout (true) - Force a vector mis-compare if the match loop times out
      # * :on_timeout_goto ("") - Optionally supply a label to branch to on timeout, by default will continue from the end of the match loop
      # * :on_pin_match_goto ("") - Optionally supply a label to branch to when pin 1 matches, by default will continue from the end of the match loop
      # * :on_pin2_match_goto ("") - Optionally supply a label to branch to when pin 2 matches, by default will continue from the end of the match loop
      # * :multiple_entries (false) - Supply an integer to generate multiple entries into the match (each with a unique readcode), this can be useful when debugging patterns with multiple matches
      # * :force_fail_on_timeout (true) - force pattern to fail if timeout occurs
      # * :global_loops (false) - whether match loop loops should use global labels
      # * :manual_stop (false) - whether to use extra cpuB flag to resolve IG-XL v.3.50.xx bug where VBT clears cpuA immediately
      #                          at start of PatFlagFunc instead of at end.  Use will have to manually clear cpuB to resume this pattern.
      # ==== Examples
      #   $tester.wait(:match => true, :time_in_us => 5000, :pin => $top.pin(:done), :state => :high)
      def match(pin, state, timeout, options = {})
        options = {
          check_for_fails:       false,
          on_timeout_goto:       false,
          pin2:                  false,
          state2:                false,
          on_pin_match_goto:     false,
          multiple_entries:      false,
          force_fail_on_timeout: true,
          global_loops:          false,
          manual_stop:           false,
          clr_fail_post_match:   false
        }.merge(options)
        options[:on_block_match_goto] ||= options.delete(:on_pin_match_goto)

        match_block(timeout, options) do |match_conditions, fail_conditions|
          # Define match conditions
          match_conditions.add do
            state == :low ? pin.expect_lo : pin.expect_hi
            cc "Check if #{pin.name} is #{state == :low ? 'low' : 'high'}"
            cycle
            pin.dont_care
          end

          if options[:pin2]
            match_conditions.add do
              state == :low ? pin.expect_hi : pin.expect_lo
              options[:state2] == :low ? options[:pin2].expect_lo : options[:pin2].expect_hi
              cc "Check if #{options[:pin2].name} is #{options[:state2] == :low ? 'low' : 'high'}"
              cycle
              options[:pin2].dont_care
              pin.dont_care
            end
          end

          # Define fail conditions
          fail_conditions.add do
            state == :low ? pin.expect_lo : pin.expect_hi
            cc "Check if #{pin.name} is #{state == :low ? 'low' : 'high'}"
            if options[:pin2]
              options[:state2] == :low ? options[:pin2].expect_lo : options[:pin2].expect_hi
              cc "Check if #{options[:pin2].name} is #{options[:state2] == :low ? 'low' : 'high'}"
            end
            cycle
            pin.dont_care
            options[:pin2].dont_care if options[:pin2]
          end
        end
      end

      # Call a match loop.
      #
      # Normally you would put your match loop in a global subs pattern, then you can
      # call it via this method. This method automatically syncs match loop naming with
      # the match generation flow, no arguments required.
      #
      # This is an IGXLBasedTester specific API.
      #
      # ==== Examples
      #   $tester.cycle
      #   $tester.call_match  # Calls the match loop, or the first entry point if you have multiple
      #   $tester.cycle
      #   $tester.call_match  # Calls the match loop, or the second entry point if you have multiple
      def call_match
        @match_counter = @match_counter || 0
        call_subroutine("match_done_#{@match_counter}")
        @match_counter += 1 unless @match_counter == (@match_entries || 1) - 1
      end

      # Apply a label to the pattern.
      #
      # No additional vector is generated.
      # Arguments:
      #    name : label name
      #  global : (optional) whether to apply global label, default=false
      #
      # ==== Examples
      #   $tester.label("something_significant")
      #   $tester.label("something_significant",true) # apply global label
      def label(name, global = false)
        global_opt = (global) ? 'global ' : ''
        microcode global_opt + name + ':' unless @inhibit_vectors
      end

      # * J750 Specific *
      #
      # Set a readcode.
      #
      # Use the set an explicit readcode for communicating with the tester. This method
      # will generate an additional vector.
      #
      # ==== Examples
      #   $tester.set_code(55)
      def set_code(code)
        cycle(microcode: "set_code #{code}")
      end

      # Branch execution to the given point.
      #
      # This generates a new vector with a jump instruction to a given label. This method
      # will generate an additional vector.
      #
      # ==== Examples
      #   $tester.branch_to("something_significant")
      def branch_to(label)
        cycle(microcode: "jump #{label}")
      end
      alias_method :branch, :branch_to

      # Add loop to the pattern.
      #
      # Pass in a name for the loop and the number of times to execute it, all vectors
      # generated by the given block will be captured in the loop.
      #
      # Optional arguments: global - whether to apply global label (default=false)
      #                label_first - whether to apply loop label before loop vector or not
      #
      # ==== Examples
      #   $tester.loop_vectors("pulse_loop", 3) do   # Do this 3 times...
      #       $tester.cycle
      #       some_other_method_to_generate_vectors
      #   end
      def loop_vectors(name, number_of_loops, global = false, label_first = false)
        if number_of_loops > 1
          @loop_counters ||= {}
          if @loop_counters[name]
            @loop_counters[name] += 1
          else
            @loop_counters[name] = 0
          end
          loop_name = @loop_counters[name] == 0 ? name : "#{name}_#{@loop_counters[name]}"
          if label_first
            global_opt = (global) ? 'global ' : ''
            microcode "#{global_opt}#{loop_name}: "
          end
          cycle(microcode: "loopA #{number_of_loops}")
          unless label_first
            global_opt = (global) ? 'global ' : ''
            cycle(microcode: "#{global_opt}#{loop_name}: ")
          end
          yield
          cycle(microcode: "end_loopA #{loop_name}")
        else
          yield
        end
      end
      alias_method :loop_vector, :loop_vectors

      # An internal method called by Origen Pattern Create to create the pattern header
      def pattern_header(options = {})
        options = {
          instruments:    {},            # Provide instruments here if desired as a hash (e.g. "mto" => "dgen_2bit")
          subroutine_pat: false,
          svm_only:       true,          # Whether 'svm_only' can be specified
          group:          false,            # If true the end pattern is intended to run within a pattern group
          high_voltage:   false,         # Supply a pin name here to declare it as an HV instrument (not yet defined)
          freq_counter:   false,     # Supply a pin name here to declare it as a frequency counter
          memory_test:    false,      # If true, define 2-bit MTO DGEN as instrument
        }.merge(options)

        if level_period?
          microcode "import tset #{min_period_timeset.name};"
        else
          called_timesets.each do |timeset|
            microcode "import tset #{timeset.name};"
          end
        end
        unless options[:group]    # Withhold imports for pattern groups, is this correct?
          called_subroutines.each do |sub_name|
            # Don't import any called subroutines that are declared in the current pattern
            microcode "import svm_subr #{sub_name};" unless local_subroutines.include?(sub_name)
          end
        end

        # If memory test, then add to instruments hash
        if options[:memory_test]
          options[:instruments].merge!('mto' => 'dgen_2bit')
        end

        if options[:svm_only]
          microcode "svm_only_file = #{options[:subroutine_pat] ? 'yes' : 'no'};"
        end

        microcode "opcode_mode = #{@opcode_mode};"
        microcode "digital_inst = #{options[:digital_inst]};" if options[:digital_inst]
        microcode 'compressed = yes;' # if $dut.gzip_patterns
        if tester.j750? && options[:freq_counter]
          # pin setup type => freq_counter is only for the J750.
          # UltraFLEX has frequency counter capability behind every pin.
          # need to make sure the pin_setup freq_count is defined after opcode_mode extended since freq count only work in extended mode
          if @opcode_mode == :extended
            microcode "pin_setup = {#{options[:freq_counter]} freq_count;}"
          else
            fail 'pin_setup freq_count can only work in extended mode'
          end
        end

        # Take care of any instruments
        options[:instruments] = options[:instruments].merge(@pushed_instrument_configs)
        if options[:instruments].length > 0
          microcode 'instruments = {'
          options[:instruments].each do |pins, instrument|
            if "#{pins}" == 'nil'
              microcode "               #{instrument};"
            elsif instrument == 'digsrc'
              microcode "               #{pins}:#{instrument} #{options[:digsrc_width]}:#{options[:digsrc_bit_order]}:#{options[:digsrc_mode]}:format=#{options[:digsrc_format]}:#{options[:digsrc_site_uniqueness]}:#{options[:digsrc_auto_cond]};"
            elsif instrument == 'digcap'
              microcode "               #{pins}:#{instrument} #{options[:digcap_width]}:#{options[:digcap_bit_order]}:#{options[:digcap_mode]}:format=#{options[:digcap_format]}:data_type=#{options[:digcap_data_type]}:#{options[:digcap_auto_cond]}:#{options[:digcap_auto_trig_enable]}:#{options[:digcap_store_stv]}:#{options[:digcap_receive_data]};"
            else
              microcode "               #{pins}:#{instrument};"
            end
          end
          microcode '}'
        end

        options[:high_voltage] = @use_hv_pin
        microcode "pin_setup = {#{options[:high_voltage]} high_voltage;}" if options[:high_voltage]
        microcode ''

        pin_list = ordered_pins.map(&:name).join(', ')

        # here indicate pattern header specific stuff
        yield pin_list
        if ordered_pins.size > 0
          max_pin_name_length = ordered_pins.map(&:name).max { |a, b| a.length <=> b.length }.length
          pin_widths = ordered_pins.map { |p| p.size - 1 }

          max_pin_name_length.times do |i|
            cc((' ' * 93) + ordered_pins.map.with_index { |p, x| ((p.name[i] || ' ') + ' ' * pin_widths[x]).gsub('_', '-') }.join(' '))
          end
        end
      end

      # An internal method called by Origen to generate the pattern footer
      def pattern_footer(options = {})
        options = {
          subroutine_pat: false,
          end_in_ka:      false,
          end_with_halt:  false,
          end_module:     true
        }.merge(options)
        $tester.align_to_last
        # cycle(:microcode => "#{$dut.end_of_pattern_label}:") if $dut.end_of_pattern_label
        if options[:end_in_ka]
          keep_alive(options)
        else
          if options[:end_with_halt]
            $tester.cycle microcode: 'halt'
          else
            if options[:end_module]
              $tester.cycle microcode: 'end_module' unless options[:subroutine_pat]
            else
              $tester.cycle
            end
          end
        end
        microcode '}'
      end

      # Returns an array of subroutines called while generating the current pattern
      def called_subroutines
        @called_subroutines ||= []
      end

      # Returns an array of subroutines created by the current pattern
      def local_subroutines # :nodoc:
        @local_subroutines ||= []
      end

      # This is an internal method use by Origen which returns a fully formatted vector
      # You can override this if you wish to change the output formatting at vector level
      def format_vector(vec)
        timeset = vec.timeset ? "> #{vec.timeset.name}" : ''
        pin_vals = vec.pin_vals ? "#{vec.pin_vals} ;" : ''
        if vec.repeat > 1
          microcode = "repeat #{vec.repeat}"
        else
          microcode = vec.microcode ? vec.microcode : ''
        end
        if vec.pin_vals && ($_testers_enable_vector_comments || vector_comments)
          comment = " // #{vec.number}:#{vec.cycle} #{vec.inline_comment}"
        else
          comment = vec.inline_comment.empty? ? '' : " // #{vec.inline_comment}"
        end

        "#{microcode.ljust(65)}#{timeset.ljust(31)}#{pin_vals}#{comment}"
      end

      # Override this to force the formatting to match the v1 J750 model (easier diffs)
      def push_microcode(code) # :nodoc:
        stage.store(code.ljust(65) + ''.ljust(31)) unless @inhibit_vectors
      end
      alias_method :microcode, :push_microcode

      # All vectors generated with the supplied block will have all pins set
      # to the repeat previous state. Any pins that are changed state within
      # the block will still update to the supplied value.
      # ==== Example
      #   # All pins except invoke will be assigned the repeat previous code
      #   # in the generated vector. On completion of the block they will
      #   # return to their previous state, except for invoke which will
      #   # retain the value assigned within the block.
      #   $tester.repeat_previous do
      #       $top.pin(:invoke).drive(1)
      #       $tester.cycle
      #   end
      def repeat_previous
        pinmap = Origen.pin_bank.pins
        pinmap.each { |id, pin| pin.repeat_previous = true }
        yield
        pinmap.each { |id, pin| pin.repeat_previous = false }
      end

      def ignore_fails(*pins)
        pins.each(&:suspend)
        yield
        pins.each(&:resume)
      end

      def enable_flag(options = {})
        options = { flagnum: 4,      # default flag to use
                }.merge(options)

        if options[:flagnum] > @flags.length
          abort "ERROR! Invalid flag value passed to 'enable_flag' method!\n"
        end
        flagname = @flags[options[:flagnum] - 1]
        update_vector(microcode: "#{@microcode[:enable]}(#{flagname})")
      end

      def set_flag(options = {})
        options = { flagnum: 4,      # default flag to use
                }.merge(options)

        if options[:flagnum] > @flags.length
          abort "ERROR! Invalid flag value passed to 'set_flag' method!\n"
        end
        flagname = @flags[options[:flagnum] - 1]
        update_vector(microcode: "#{@microcode[:set_flag]}(#{flagname})")
      end

      def cycle(options = {})
        # handle overlay if requested
        ovly_style = nil
        if options.key?(:overlay)
          ovly_style = options[:overlay][:overlay_style].nil? ? @overlay_style : options[:overlay][:overlay_style]
          overlay_str = options[:overlay][:overlay_str]

          # route the overlay request to the appropriate method
          case ovly_style
            when :subroutine, :default
              subroutine_overlay(overlay_str, options)
              ovly_style = :subroutine
            when :label, :global_label
              options[:dont_compress] = true
              unless @overlay_history.key?(overlay_str)
                # J750 behavior is local label not global
                label "#{overlay_str}", (ovly_style == :global_label)
                @overlay_history[overlay_str] = { is_label: true }
              end
            when :digsrc
              if ultraflex?
                cur_pin_state = options[:overlay][:pins].state.to_sym
                options[:overlay][:pins].drive_mem
                options = digsrc_overlay(options)
              else
                ovly_style = overlay_style_warn(options[:overlay][:overlay_str], options)
              end
            else
              ovly_style = overlay_style_warn(options[:overlay][:overlay_str], options)
          end # case ovly_style
        else
          @overlay_subr = nil
        end # of handle overlay

        options_overlay = options.delete(:overlay) if options.key?(:overlay)
        unless ovly_style == :subroutine
          if @mask_vector
            # tack on masking opcodes
            super(options.merge(microcode: "#{options[:microcode]} #{@microcode[:mask_vector]}"))
          else
            super(options)
          end
        end # unless ovly_style

        unless options_overlay.nil?
          options_overlay[:pins].state = cur_pin_state if ovly_style == :digsrc
          # stage = :body if ovly_style == :subroutine 		# always set stage back to body in case subr overlay was selected
        end
      end

      # Warn user of unsupported overlay style
      def overlay_style_warn(overlay_str, options)
        Origen.log.warn("Unrecognized overlay style :#{@overlay_style}, defaulting to subroutine")
        Origen.log.warn('Available overlay styles :label, :global_label, :subroutine') if j750? || j750_hpt?
        Origen.log.warn('Available overlay styles :digsrc, :digsrc_subroutine, :label, :global_label, :subroutine') if ultraflex?
        subroutine_overlay(overlay_str, options)
        @overlay_style = :subroutine		# Just give 1 warning
      end

      # Call this method at the start of any digsrc overlay operations, this method
      # takes care of all the microcodes and delays that's needed for digsrc overlay
      # Required arguments:
      #                     pins
      # Optionsal arguments:
      #                     options[:dssc_mode] = :single or :dual, anything else wil be
      #                     treated as if it's operating in :quad mode
      def digsrc_start(pins, options = {})
        options = {
          dssc_mode: :single # defaults dssc_mode to single mode
        }.merge(options)
        if pins.size > 1
          microcode "((#{format_multiple_instrument_pins(pins)}):DigSrc = Start)"
        else
          microcode "((#{pin}):DigSrc = Start)"
        end
        if options[:dssc_mode] == :single
          $tester.cycle(repeat: 145) # minimum of 144 cycles, adding 1 for safey measures
        elsif options[:dssc_mode] == :dual
          $tester.cycle(repeat: 289) # minimum of 288 cycles, adding 1 for safety measures
        else
          $tester.cycle(repeat: 577) # minimum of 577 cycles, adding 1 for safety measures
        end
      end

      # Call this method at the end of each digscr overlay operation to clear the pattern
      # memory pipeline, so that the pattern is ready to do the next digsrc overlay operation.
      # Required arguments:
      #                     pins
      def digsrc_stop(pins, options = {})
        if pins.size > 1
          microcode "((#{format_multiple_instrument_pins(pins)}):DigSrc = Stop)"
        else
          microcode "((#{pins}):DigSrc = Stop)"
        end
      end

      # Call this method before each tester cycle to insert the digsrc overlay microcode
      def digsrc_send(pins)
        microcode "((#{format_multiple_instrument_pins(pins)}):DigSrc = SEND)"
      end

      # Call this method before each tester cycle to inser the digcap overlay microcode
      def digcap_store(pins)
        microcode "((#{format_multiple_instrument_pins(pins)}):DigCap = STORE)"
      end

      def apply_digsrc_settings(options = {})
        options.merge!(digsrc_width: 1) if options[:digsrc_width].nil? # default to digsrc 1
        options.merge!(digsrc_bit_order: :lsb) if options[:digsrc_bit_order].nil? # default to lsb
        options.merge!(digsrc_mode: :serial) if options[:digsrc_mode].nil? # default to serial mode
        options.merge!(digsrc_format: :binary) if options[:digsrc_format].nil? # default to binary
        options.merge!(digsrc_site_uniqueness: :unique_sites) if options[:digsrc_site_uniqueness].nil? # default to unique sites
        options.merge!(digsrc_data_type: :default) if options[:digsrc_data_type].nil? # default to default
        options.merge!(digsrc_auto_cond: :auto_cond_disable) if options[:digsrc_auto_cond].nil? # default to disable
        @digsrc_settings = options
      end

      def apply_digcap_settings(options = {})
        options.merge!(digcap_width: 8) if options[:digcap_width].nil? # default to digcap 8
        options.merge!(digcap_bit_order: :lsb) if options[:digcap_bit_order].nil? # default to lsb
        options.merge!(digcap_mode: :serial) if options[:digcap_mode].nil? # default to serial mode
        options.merge!(digcap_site_uniqueness: :unique_sites) if options[:digcap_site_uniqueness].nil? # default to unique sites
        options.merge!(digcap_format: :binary) if options[:digcap_format].nil? # default to binary
        options.merge!(digcap_data_type: :default) if options[:digcap_data_type].nil? # default to default
        options.merge!(digcap_auto_cond: :auto_cond_disable) if options[:digcap_auto_cond].nil? # default to disable
        options.merge!(digcap_auto_trig_enable: :auto_trig_disable) if options[:digcap_auto_trig_enable].nil? # default to disable
        options.merge!(digcap_store_stv: :store_stv_disable) if options[:digcap_store_stv].nil? # default to disable
        options.merge!(digcap_receive_data: :store_stv_disable) if options[:digcap_receive_data].nil? # default to logic
        @digcap_settings = options
      end

      # Call this method if there are more than one pin/pin groups used with an instrument,
      # this method will format an array of pins into the correct format required by igxl
      # pattern microcodes.
      def format_multiple_instrument_pins(pins, options = {})
        return_value = ''
        pins.each do |pin|
          return_value += "#{pin}"
          return_value += ',' unless pins.last == pin
        end
        return_value
      end

      def mask_fails(setclr)
        @mask_vector = setclr
      end

      # Similar to push_microcode, but for the instrument statement in the pattern header
      #
      # @example
      #   tester.push_instrument 'SAR_IN_1', 'UltraSource'
      #   # results in the below line added
      #   # instruments = {
      #   #      SAR_IN_1:UltraSource;
      #   # }
      def push_instrument(pin_spec, instrument_def)
        @pushed_instrument_configs[pin_spec] = instrument_def
      end

      # Implement subroutine overlay, called by tester.cycle
      def subroutine_overlay(sub_name, options = {})
        if @overlay_subr != sub_name
          # unless last staged vector already has the subr call do the following
          i = -1
          i -= 1 until stage.bank[i].is_a?(OrigenTesters::Vector)
          if stage.bank[i].microcode !~ /call #{sub_name}/

            # check for repeat on new last vector, unroll 1 if needed
            if stage.bank[i].repeat > 1
              v = OrigenTesters::Vector.new
              v.pin_vals = stage.bank[i].pin_vals
              v.timeset = stage.bank[i].timeset
              stage.bank[i].repeat -= 1
              stage.store(v)
              i = -1
            end

            # mark last vector as dont_compress
            stage.bank[i].dont_compress = true
            # insert subroutine call
            call_subroutine sub_name
          end # if microcode not placed
          @overlay_subr = sub_name
        end

        # stage = sub_name
      end # subroutine_overlay

      # Disable the automatic addition of the digsrc start command at the beginning of the pattern
      #
      # @example
      #   tester.digsrc_skip_start :pin_or_group_name
      #
      def digsrc_skip_start(pin_or_group)
        pin_or_group = dut.pin(pin_or_group).name
        if @overlay_history[pin_or_group].nil?
          @overlay_history[pin_or_group] = { count: 0, is_digsrc: true, start_applied: true }
        else
          Origen.log.warn 'tester.digsrc_skip_start must be called before any overlay actions on the pin'
        end
      end

      # Perform digsrc overlay (called by tester.cycle)
      def digsrc_overlay(options = {})
        options[:overlay] = { change_data: true }.merge(options[:overlay])
        pin_name = dut.pin(options[:overlay][:pins]).name
        repeat_count = options[:repeat].nil? ? 1 : options[:repeat]

        if options[:overlay][:change_data]
          # add the send microcode
          microcode "((#{pin_name}):DigSrc = SEND)"

          # keep track of amount of digsrc used for header comment
          if @overlay_history[pin_name].nil?
            @overlay_history[pin_name] = { count: repeat_count, is_digsrc: true }
          else
            @overlay_history[pin_name][:count] += repeat_count
          end

          # ensure no unwanted repeats on the send vector
          options[:dont_compress] = true

          # ensure start microcode is placed at the beginning of the pattern
          if @overlay_history[pin_name][:start_applied].nil?
            # insert start microcode at the beginning of the pattern
            stage.insert_from_start "((#{pin_name}):DigSrc = Start)", 0
            @overlay_history[pin_name][:start_applied] = true

            # get the first vector of the pattern
            i = 0
            i += 1 until stage.bank[i].is_a?(OrigenTesters::Vector)
            first_vector = stage.bank[i]

            # insert a copy of the first vector with no repeats
            unless first_vector.inline_comment == 'added for digsrc start opcode'
              v = OrigenTesters::Vector.new
              v.pin_vals = stage.bank[i].pin_vals
              v.timeset = stage.bank[i].timeset
              v.inline_comment = 'added for digsrc start opcode'
              v.dont_compress = true
              stage.insert_from_start v, i

              # decrement repeat count of previous first vector if > 1
              first_vector.repeat -= 1 if first_vector.repeat > 1
            end

            # get cycle count up to this point, add repeat to beginning if needed
            cycle_count = -1
            stage.bank.each { |v| cycle_count += v.repeat if v.is_a?(OrigenTesters::Vector) }
            if cycle_count < @dssc_send_delay
              d = OrigenTesters::Vector.new
              d.pin_vals = first_vector.pin_vals
              d.timeset = first_vector.timeset
              d.inline_comment = 'added for dssc start to send delay'
              d.repeat = @dssc_send_delay - cycle_count

              # get the first vector of the pattern
              i = 0
              i += 1 until stage.bank[i].is_a?(OrigenTesters::Vector)

              # insert new vector after the first vector
              stage.insert_from_start d, i + 1
            end

          end # of place start microcode

        end # if options[:change_data]
        options
      end # digsrc overlay
    end
  end
end
